#ifndef CORE_RANDOM_HPP
#define CORE_RANDOM_HPP

#include <random>

#include "core/exception.hpp"
#include "core/log.hpp"

/**
 * \file
 * \ingroup random
 * Pseudo-random number generators (RNG) and distributions (RND) based on c++11 <random>
 */

namespace cas
{
    /**
     * \ingroup core
     * \defgroup random Random Number Generators
     * RNG & RND based on c++11 <random>
     */

    /** \brief random seed type */
    typedef uint_fast64_t rng_seed_t;

    /**
     * \ingroup random
     *  Base class of all RNGs
     */
    template <typename T = uint64_t, class R = std::mt19937_64>
    class RNG
    {
    public:
        virtual ~RNG() = default;

        inline virtual T operator()() { return T{}; }
        virtual void setSeed(rng_seed_t seed) { this->seed = seed; }
        virtual rng_seed_t getSeed() const { return seed; }

        static constexpr rng_seed_t DefaultSeed = 0x0125e591;

    protected:
        rng_seed_t seed;
        R gen;
    };

    /**
     * \ingroup random
     *  Mersenne Twister Number Generators
     */
    template <typename T = uint64_t>
    class MersenneTwisterRNG : public RNG<T>
    {
        friend class UniformRealRND;
        friend class UniformInt64RND;

    public:
        MersenneTwisterRNG();
        MersenneTwisterRNG(rng_seed_t seed);
        MersenneTwisterRNG(const MersenneTwisterRNG<T> &mt);
        inline T operator()() override { return static_cast<T>(gen()); }
        void setSeed(rng_seed_t seed) override;

    private:
        std::mt19937_64 gen; // redefined
    };

    template class MersenneTwisterRNG<uint64_t>;

    /**
     * \ingroup random
     * Base class of all RNDs
     */
    template <typename T, class R = MersenneTwisterRNG<>, typename RT = uint_fast64_t>
    class RND
    {
        static_assert(std::is_base_of<RNG<RT>, R>::value);

    public:
        virtual ~RND() = default;

        inline virtual T operator()() { return (*rng)(); }
        inline virtual void setRng(std::shared_ptr<R> r) { rng = r; }

    protected:
        std::shared_ptr<R> rng;
    };

    /** \ingroup random */
    class Int64RND : public RND<int64_t>
    {
    public:
        virtual void setParam() = 0;
    };

    /** \ingroup random */
    class UniformRealRND : public RND<double>
    {
    public:
        UniformRealRND();
        inline double operator()() override { return uni(rng->gen); }

        void setParam(const double &a, const double &b);

    private:
        std::uniform_real_distribution<double> uni;
    };

    /** \ingroup random */
    class UniformInt64RND : public Int64RND
    {
    public:
        UniformInt64RND();
        UniformInt64RND(const UniformInt64RND &u);
        inline int64_t operator()() override { return uni(rng->gen); }

        void setParam() override { SFATAL_ERROR_CONT("Don't call abstract method."); };
        void setParam(const int64_t &a, const int64_t &b);

    private:
        std::uniform_int_distribution<int64_t> uni;
    };
}

#endif /* CORE_RANDOM_HPP */